/*
 * Copyright (c) 2010-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "./GLPrivate.h"
#include "alkes/opengl/GLImageBuffer.h"

#define DEBUG_BUFFER    false

namespace alkes {

uint32_t clp2(uint32_t x)
{
    x = x - 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >> 16);
    return x + 1;
}

GLImageBuffer* GLImageBuffer::create(uint16_t image_width, uint16_t image_height, PixelFormat format, bool write_only)
{
#if 1
    // TODO: check support "non power of 2" texture.
    uint16_t buf_width = (uint16_t)clp2(image_width);
    uint16_t buf_height = (uint16_t)clp2(image_height);
#endif
    int32_t pitch = alkes_get_bytelength(format, buf_width);
    ImageBufferCaps caps;
    caps.image_width = image_width;
    caps.image_height = image_height;
    caps.buf_width = buf_width;
    caps.buf_height = buf_height;
    caps.format = format;

    if (!validate(caps, pitch))
        return 0;

    return new GLImageBuffer(caps, pitch, write_only);
}

void GLImageBuffer::destroy()
{
    delete this;
}

GLImageBuffer::GLImageBuffer(const ImageBufferCaps& caps, int32_t pitch, bool write_only)
: MemoryImageBuffer(caps, pitch, write_only)
, lock_image_(0)
{
}

GLImageBuffer::~GLImageBuffer()
{
    if (flag(AL_TEXFLAG_GENERATED))
    {
        AL_OGL_INVOKE(glDeleteTextures(1, &texture_));
        LOGV_IF(DEBUG_BUFFER, "glDeleteTextures(%d)", texture_);
        TRACE_IF(DEBUG_BUFFER);
    }
}

bool GLImageBuffer::createWriteBuffer(const Rect& rect, bool readable)
{
    // Already created.
    if (lock_image_)
        return true;    // do nothing.

    ImageBufferCaps caps(getCaps());
    int32_t pitch;
    bool sub_image = false;
    if (!flag(AL_TEXFLAG_GENERATED))
    {
        // Create all size.
        pitch = getPitch();
    }
    else
    {
        // For sub image.
        caps.buf_width = rect.width;
        caps.buf_height = rect.height;
        caps.image_width = rect.width;
        caps.image_height = rect.height;
        pitch = alkes_get_bytelength(caps.format, rect.width);
        sub_image = true;
    }

    lock_image_ = HeapImageBuffer::create(caps, pitch);

    if (sub_image && readable)
    {
        // copy from system memory.
        AL_ASSERT(false);   // not support
    }

    return 0 != lock_image_;
}

void* GLImageBuffer::onLockRect(const Rect& rect, uint32_t lock_mode, int32_t* pitch)
{
    if (!createWriteBuffer(rect, 0 != (lock_mode & AL_LOCKMODE_READ)))
        return 0;

    void* locked = lock_image_->lockRect(&rect, lock_mode, pitch);
    if (!locked)
        return 0;

    return locked;
}

void GLImageBuffer::onUnlockRect(const Rect& locked_rect)
{
    bool clear_image = false;

    if (flag(AL_BUFATTR_LOCK_FOR_WRITE))
    {
        if (flag(AL_TEXFLAG_GENERATED))
        {
            GLenum format, type;
            uint16_t width, height;

            PixelFormat alformat = getPixelFormat();
            lock_image_->getSize(width, height);

            alkes_format2gl(alformat, format, type);

            LOGV_IF(DEBUG_BUFFER, "onUnlockRect(alformat=%08x, format=%04x, type=%04x)", alformat, format, type);

            AL_OGL_INVOKE(glBindTexture(GL_TEXTURE_2D, texture_));
            AL_OGL_INVOKE(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
            AL_OGL_INVOKE(glTexSubImage2D(
                GL_TEXTURE_2D, 0,
                locked_rect.x, locked_rect.y, locked_rect.width, locked_rect.height,
                format, type, lock_image_->getBufferPtr()));

            clear_image = true;
        }
        else
        {
            // do nothing.
        }
    }
    else if (flag(AL_BUFATTR_LOCK_FOR_READ) && flag(AL_TEXFLAG_GENERATED))
    {
        clear_image = true;
    }

    lock_image_->unlockRect();

    if (clear_image)
        lock_image_ = 0;
}

bool GLImageBuffer::generateTexture()
{
    if (!lock_image_)
        return false;

    GLenum format, type;
    uint16_t width, height;

    PixelFormat alformat = getPixelFormat();
    lock_image_->getSize(width, height);

    alkes_format2gl(alformat, format, type);

    LOGV_IF(DEBUG_BUFFER, "onUnlockRect(alformat=%08x, format=%04x, type=%04x)", alformat, format, type);

    GLint internalformat = GL_RGBA;
    switch (alkes_get_pixel_format_traits(alformat)->component_num)
    {
    case 1:
        internalformat = GL_ALPHA;
        break;
    case 3:
        internalformat = GL_RGB;
        break;
    case 4:
        internalformat = GL_RGBA;
        break;
    }

    AL_OGL_INVOKE(glGenTextures(1, &texture_));

    TRACE_IF(DEBUG_BUFFER);
    LOGV_IF(DEBUG_BUFFER, "glGenTextures() = %d", texture_);

    AL_OGL_INVOKE(glBindTexture(GL_TEXTURE_2D, texture_));
    AL_OGL_INVOKE(glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, type, lock_image_->getBufferPtr()));

    AL_OGL_INVOKE(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    AL_OGL_INVOKE(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));

    // destroy image
    lock_image_ = 0;

    // success to generate texture.
    setFlag(AL_TEXFLAG_GENERATED);

    return true;;
}

bool GLImageBuffer::getTexture(GLuint& texture)
{
    bool generated = flag(AL_TEXFLAG_GENERATED);

    if (!generated)
        generated = generateTexture();

    if (generated)
        texture = texture_;

    return generated;
}

}
